﻿using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Linq;

using RAD.LightOne;
using RAD.LightOne.Cache;
using RAD.LightOne.Data;
using RAD.LightOne;
using System.Diagnostics;

namespace RAD.LightOne.Cache
{
    #region Documentation
    /// <summary>
    /// Classe que implementa e controla o cache de objetos.
    /// </summary>
    #endregion Documentation
    internal sealed class MyObjectsCache : IDisposable
    {
        #region Inner Types

        private struct RemoveSheduleArgs
        {
            public int milliseconds;

            public Guid key;
        }

        #endregion

        #region Fields

        #region Documentation
        /// <summary>
        /// Prefixo de identificação no cache de consultas, das operações utilizadas no cache
        /// de objetos.
        /// </summary>
        #endregion
        private const string PREFIX = "CACHE.";

        #region Documentation
        /// <summary>
        ///Dicionário de cache exclusivo para objetos marcados com o atributo Cacheable.
        ///</summary>
        #endregion
        private static Dictionary<Guid, MyObject> strongCache;

        #region Documentation
        ///<summary>
        /// Dicionário que funciona como um cache de forma a manter a referência a objetos 
        /// recentes sem no entanto impedir sua coleta pelo GC, tal referência fraca pode acelerar 
        /// o processo de carregamento de instâncias, poupando processamento até por durar um pouco mais
        /// que o cache representado pelo slot de objetos da transação.
        ///</summary>
        #endregion
        private static Dictionary<Guid, WeakReference<MyObject>> weakCache;
        private MyDbTransaction trx;

        #endregion Fields

        #region Properties

        public MyObject this[Guid key]
        {
            get
            {
                try
                {
                    #region Comments
                    /*
                     * A verificação de objeto borrado foi retirada da lógica uma vez que existem formas
                     * de verificação direta do estado "borrado" de um objeto e o mesmo pode ser recarregado
                     * a um estado consistente através do método "Load".
                     */
                    #endregion
                    MyObject clone;
                    if (strongCache.ContainsKey(key))
                    {
                        clone = strongCache[key];
                        CacheableAttribute attrib = CacheableAttribute.GetDefinedAttribute(clone.GetType());
                        if (!attrib.SupressBlurredVerify && this.Blurred(strongCache[key]))
                        {
                                strongCache.Remove(key);
                                return null;
                        }
                    }
                    else if (weakCache.ContainsKey(key))
                    {
                        clone = weakCache[key].Target;
                        CacheableAttribute attrib = CacheableAttribute.GetDefinedAttribute(clone.GetType());
                        if (weakCache[key].IsAlive && (!attrib.SupressBlurredVerify && this.Blurred(weakCache[key].Target)))
                        {
                            weakCache.Remove(key);
                            return null;
                        }
                        GC.KeepAlive(weakCache[key]);
                    }
                    else
                    {
                        return this.trx.Slot[key];
                    }

                    if (this.trx.Slot.ConstainsKey(key))
                    {
                        this.trx.Slot[key] = clone;
                    }
                    else
                    {
                        this.trx.Slot.Add(clone);
                    }

                    return this.trx.Slot[key];
                }
                catch
                {
#if DEBUG
                    string error;
#endif
                    return null;
                }
            }
        }

        #endregion Properties

        #region Constructors

        static MyObjectsCache()
        {
            strongCache = new Dictionary<Guid,MyObject>();
            weakCache = new Dictionary<Guid, WeakReference<MyObject>>();
        }

        public MyObjectsCache(IDbTransaction trx)
        {
            if (trx is MyDbTransaction)
            {
                if (trx == null) throw new ArgumentNullException("If passed the transaction don't be null.");
                this.trx = (MyDbTransaction) trx;

                this.trx.Commited += new EventHandler(trx_Commited);
            }
            else
            {
                throw new ArgumentException("The real type of transaction shall we MyDbTransaction.");
            }
        }

        ~MyObjectsCache()
        {
            #region Comments
            /*
             * Destrutor da classe de cache, vai além, realiza um processamento liberando as chaves no cahce fraco
             * para as classes que já foram coletadas pelo GC.
            */
            #endregion
            try
            {
                Guid[] keys = new Guid[weakCache.Count];
                weakCache.Keys.CopyTo(keys, 0);
                foreach (Guid key in keys)
                {
                    try
                    {
                        if (!weakCache[key].IsAlive || key == Guid.Empty) weakCache.Remove(key);
                    }
                    catch
                    {
                        continue;
                    }
                }
                keys = null;
            }
            catch { }
            /*Thread thr = new Thread(new ThreadStart(Release));
            thr.Name = "Release CACHE";
            thr.Priority = ThreadPriority.Lowest;
            thr.Start();

        }

        private void Release()
        {
            Guid[] keys = new Guid[weakCache.Count];
            weakCache.Keys.CopyTo(keys, 0);
            foreach (Guid key in keys)
            {
                if (!weakCache[key].IsAlive) weakCache.Remove(key);
            }
            keys = null;
*/
        }

        #endregion

        #region Methods

        #region Documentation
        /// <summary>
        /// Realiza uma avaliação do estado "borrado" do objeto e recursivamente em suas propriedades <see cref="MyObject"/>.
        /// </summary>
        /// <param name="target">Objeto a ser testado.</param>
        /// <returns>True=Se o objeto é válido, False se está borrado.</returns>
        #endregion
        private bool NotBlurred(MyObject target)
        {
            if (this.Blurred(target))
            {
                return false;
            }
            else
            {
                this.ReloadBlurredFields(target);
            }

            return true;
                        
        }

        private void trx_Commited(object sender, EventArgs e)
        {
            foreach (WeakReference<MyObject> weak in this.trx.Slot)
            {
                if (weak.IsAlive && CacheableAttribute.IsDefined(weak.Target))
                {
                    AddToStrongCache(weak.Target);
                }
                else if (weak.IsAlive)
                {
                    AddToWeakCache(weak.Target.Id, weak);
                }
            }
        }

        #region Documentation
        /// <summary>
        /// Método auxiliar que adiciona/substitui um item do cache forte.
        /// </summary>
        #endregion
        private void AddToStrongCache(MyObject item)
        {
            try
            {
                if (strongCache.ContainsKey(item.Id))
                {
                    strongCache[item.Id] = item;
                }
                else
                {
                    strongCache.Add(item.Id, item.Clone(item.Id));
                    ScheduledRemove(item.Id, CacheableAttribute.GetDefinedAttribute(item.GetType()).LifeTime);
                }
                
            }
            catch { }
        }

        #region Documentation
        /// <summary>
        /// Método auxiliar que adiciona/substitui um item do cache médio.
        /// </summary>
        #endregion
        private void AddToWeakCache(Guid key, WeakReference<MyObject> item)
        {
            try
            {
                if (weakCache.ContainsKey(key))
                {
                    weakCache[key] = item;
                    GC.KeepAlive(item);
                }
                else
                {
                    weakCache.Add(key, new WeakReference<MyObject>(item.Target.Clone(key)));
                }
            }
#if DEBUG
            catch (Exception except)
            {
                throw new InvalidOperationException("AddToWeakCache causes exception.", except);
            }
#else
            catch { }
#endif
        }

        #region Documentation
        /// <summary>
        /// Verifica a integridade das propriedades que SÃO MyObjects, em sendo borradas,
        /// um novo find além de restaurá-las irá fazer com que sejam corretamente recarregadas
        /// para o slot da transação.
        /// </summary>
        #endregion
        private void ReloadBlurredFields(MyObject clone)
        {
            foreach (FieldInfo fInfo in MetadataReader.GetMyObjectFields(clone.GetType()))
            {
                if (Blurred(fInfo.GetValue(clone) as MyObject))
                {
                    Finder finder = new Finder();
                    fInfo.SetValue(clone, finder.FindById(fInfo.FieldType, ((MyObject)fInfo.GetValue(clone)).Id, this.trx));
                }
            }
        }

        #region Documentation
        /// <summary>
        /// Cria um "agendamento" de remoção, através de uma thread em separado, do cache forte.
        /// </summary>
        /// <param name="key">id do objeto que será removido</param>
        /// <param name="seconds">tempo em segundos até a remoção</param>
        #endregion
        private void ScheduledRemove(Guid key, int seconds)
        {
            if (seconds > 0)
            {
                Thread thr = new Thread(new ParameterizedThreadStart(StrongCacheSheduledRemove));
                thr.Name = key.ToString();
                thr.IsBackground = true;
                thr.Priority = ThreadPriority.Lowest;
                
                RemoveSheduleArgs args;
                args.key = key;
                args.milliseconds = seconds * 1000;

                thr.Start(args as object);
            }
        }

        private void StrongCacheSheduledRemove(object removeSheduleArgs)
        {
            try
            {
                Thread.Sleep(((RemoveSheduleArgs)removeSheduleArgs).milliseconds);
                strongCache.Remove(((RemoveSheduleArgs)removeSheduleArgs).key);
            }
            catch { }
        }

        #region Documentation
        /// <summary>
        /// Adiciona ou substitui um item do cache.
        /// </summary>
        /// <param name="item">item a ser adicionado ao cache.</param>
        #endregion Documentation
        public void Add(MyObject item)
        {
            if(!this.trx.Slot.Contains(item))
            this.trx.Slot.Add(item);

            if (CacheableAttribute.IsDefined(item))
            {
                CacheableAttribute attrib = CacheableAttribute.GetDefinedAttribute(item.GetType());
                switch(attrib.Mode)
                {
                    case CacheMode.Strong:
                        this.AddToStrongCache(item);
                        break;
                    case CacheMode.Weak:
                        this.AddToWeakCache(item.Id, new WeakReference<MyObject>(item));
                        break;
                }
            }

        }

        #region Documentation
        /// <summary>
        /// Limpa totalmente o cache, inclusive do slot da transação.
        /// </summary>
        #endregion
        public void Clear()
        {
            strongCache.Clear();
            weakCache.Clear();
            this.trx.Slot.Clear();
        }

        public bool ContainsKey(Guid key)
        {
            return (strongCache.ContainsKey(key) || this.trx.Slot.ConstainsKey(key) || weakCache.ContainsKey(key));
        }

        public bool Contains(MyObject item)
        {
            return (ContainsKey(item.Id));
        }

        [System.Obsolete("This operation is invalid for this collection.",true)]
        public void CopyTo(MyObject[] array, int arrayIndex)
        {
            throw new InvalidOperationException("This operation is invalid for this collection.");
        }

        public bool Remove(MyObject item)
        {
            return (strongCache.Remove(item.Id) || weakCache.Remove(item.Id) || this.trx.Slot.Remove(item));
        }

        public bool RemoveKey(Guid key)
        {
            return (strongCache.Remove(key) || weakCache.Remove(key) || this.trx.Slot.RemoveKey(key));
        }

        #region Documentation
        /// <summary>
        /// Método que cria uma string sql, ligada a um tipo específico para verificação
        /// de concorrência otimista.
        /// </summary>
        /// <param name="type">tipo analizado.</param>
        #endregion
        private string MakeVerificationQuery(Type type)
        {
            Configuration config = Configuration.Instance();

            SelectQueryCache queryCache = SelectQueryCache.Instance();
            if (queryCache.Contains(PREFIX + type.FullName))
            {
                return queryCache[PREFIX + type.FullName].ToString();
            }
            else
            {
                InternalObj obj = InternalObjBuilder.GetReadStructure(type, DataOperations.Select);

                foreach (Table tItem in obj.Where(t => t.Where(s => s.NameOnObject == "lastPersistence").FirstOrDefault() != null))
                {
                    StringBuilder sql = new StringBuilder(string.Format("SELECT COUNT({0}{6}{1}) FROM {0}{2}{1} WHERE {0}{5}{1}>{3} AND {0}{6}{1}={4}", config.QuotePrefix, config.QuoteSuffix, tItem.Name, @"{0}", @"{1}", tItem[(int)KeyField.LastPersistence].Name, tItem[(int)KeyField.ID].Name));
                    queryCache.Add(new KeyValuePair<string, StringBuilder>(PREFIX + type.FullName, sql));
                    return sql.ToString();
                }
            }
            throw new InvalidOperationException("This operation is aborted by incapacity to resolve a verification query in MyObjectsCache class.");
        }

        #region Documentation
        /// <summary>
        /// Verifica se a instância passada está desatualizada em relação ao banco de dados,
        /// ou seja, suja, borrada. Deve ser recarregada antes de ser entregue a quem a requisitou.
        /// </summary>
        /// <param name="instance">instância que será analizada.</param>
        /// <returns>true=sujo.</returns>
        #endregion
        private bool Blurred(MyObject instance)
        {
            DBEngine dbe;
            dbe = DBEngine.GetInstance(this.trx);

            int count = Convert.ToInt32(dbe.ExecuteScalar(this.MakeVerificationQuery(instance.GetType()), instance.LastPersistence, instance.Id));
            return (count > 0);
        }

        #endregion Methods

        #region IDisposable Members

        public void Dispose()
        {
            this.trx = null;
        }

        #endregion
    }

}
